09. 설계의 건전성을 해치는 여러 악마

📌 Contents

📌 데드 코드

  • 절대로 실행되지 않는 조건 내부에 있는 코드를 데드 코드(dead code) 또는 도달 불가능한 코드(unreachable code)라고 부름
  • 이 악마는 코드의 가독성을 떨어뜨림
  • 또한 언젠가 버그가 될 가능성도 있음. 사양 변경에 의해 좀비처럼 되살아날 수 있음. 이 때 되살아난 코드와 사양이 다르면 버그가 됨
  • 데드 코드는 발견하는 즉시 제거하는 것이 좋음

📌 YAGNI 원칙

  • 개발을 할 때 미래를 예측하고, 미리 만들어 두는 경우가 있음
  • 이렇게 미리 구현한 로직은 실제로 거의 사용되지도 않고, 버그의 원인이 되기도 함
  • YANGI라는 소프트웨어 원칙이 있음
  • 'You Aren't Gonna Need it.'의 약자로, '지금 필요 없는 기능을 만들지 말라!'라는 의미
  • 실제로 지금 당장 필요한 것들만 만들라는 방침
  • 소프트웨어에 대한 요구는 매일매일 변함
  • 사양으로 확정되지 않고 명확하게 언어화되지 않은 요구를 미리 예측하고 구현해도, 이러한 예측은 대부분 맞지 않음
  • 예측에 들어맞지 않는 로직은 데드 코드가 됨. 또, 이렇게 만들어진 로직은 일반적으로 복잡함

📌 매직 넘버

  • 설명이 없는 숫자는 개발자를 혼란스럽게 만듬
  • 로직 내부에 직접 작성되어 있어서, 의미를 알기 힘든 숫자를 매직 넘버라고 부름
  • 매직 넘버를 사용하지 않으려면, 상수를 활용하면 됨
  • 서비스를 바로 동작시켜 보고 싶을 때, 매직 넘버를 사용하기 쉽지만, 리포지터리에 커밋할 때는 값을 상수로 변경하고 커밋하는 것이 좋음

📌 문자열 자료형에 대한 집착

  • 읽어 들인 CSV 파일에서 데이터를 추출할 때, split 메서드를 사용하는 경우가 있음
  • 하지만 그런 용도가 아닌데, 의미가 다른 여러 개의 값을 하나의 String 변수에 무리하게 넣으면, 의미를 알기 어려움
  • 또한 split 메서드 등을 활용하므로 로직이 쓸데없이 복잡해져서 가독성이 크게 저하됨
  • 이는 5장에서 설명했던 기본 자료형에 대한 집착처럼 클래스뿐만 아니라 변수마저 추가하지 않으려는 경향 때문에 이런 코드가 생겨남
  • 의미가 다른 값은 각각 다른 변수에 저장하는 것이 좋음

📌 전역 변수

  • 모든 곳에서 접근할 수 있는 변수를 전역 변수라고 부름
  • 자바 언어 사양에는 전역 변수가 없지만, 변수를 public static으로 선언하면, 모든 곳에서 접근할 수 있음
  • 모든 곳에서 참조할 수 있고 조작도 할 수 있는 변수이므로, 어떻게 보면 편리한 기능이라고 생각할 수도 있지만 실제로는 반대임


  • 여러 로직에서 전역 변수를 참조하고 값을 변경하면, 어디에서 어떤 시점에 값을 변경했는지 파악하기 대단히 힘듭니다.
  • 전역 변수를 참조하고 있는 로직을 변경해야 할 때, 해당 변수를 참조하는 다른 로직에서 버그가 발생하는지 검토해야 함
  • 동기화가 필요한 경우에도 문제가 발생함. 동기화는 제대로 설계하지 않으면 락을 얻기 위해 대기하는 시간이 길어져서 퍼포먼스를 크게 떨어뜨림. 또한 동기화 문제가 생기면, 데드락 상태에 빠질 수 있음
  • 전역으로 선언된 변수만이 전역 변수의 '성질'을 갖는 것은 아님
  • 거대 데이터 클래스도 전역 변수와 같은 '성질'을 띠는 경우가 많음
  • 너무 많은 데이터를 가지고 있으므로, 여러 곳에서 참조하기 때문임
  • 또한, 동기화와 관련해서도 거대 데이터 클래스는 전역 변수보다 훨씬 악질적임
  • 동기화를 하고 싶은 인스턴스 변수가 하나뿐이라고 해도, 해당 인스턴스의 다른 인스턴스 변수까지 모두 잠그므로(lock), 성능상 문제가 큼
  • 설계가 제대로 이루어지지 않은 시스템에서 거대 데이터 클래스가 아주 쉽게 만들어짐
  • 전역 변수를 직접적으로 사용하지 않더라도, 전역 변수와 같은 개념을 알게 모르게 사용하고 있는 것임


  • 전역 변수는 영향 범위가 너무 넓기 떄문에, 여러 곳에서 호출할 수 있는 구조이고, 호출되기 쉬운 구조임
  • 영향 범위가 가능한 한 좁게 설계해야 함. 관계없는 로직에서는 접근할 수 없게 설계해라
  • 전역 변수를 사용하고 싶다면, 정말로 필요한지 검토 필수
  • 전역 변수로 만들기보다는 최대한 한정된 클래스에서만 접근할 수 있는 형태로 설계해라

📌 null 문제

  • null이 들어갈 수 있다고 전제하고 로직을 만들면, 모든 곳에서 null 체크를 해야함
  • 결국 null 체크 코드가 너무 많아져서 가독성이 떨어질 것이고, 실수로 null 체크를 안 하는 곳이 생기면 곧바로 버그가 될 것임
  • null은 초기화하지 않은 메모리 영역에서 값을 읽는 것을 피하기 위해 발명된 것임
  • null은 메모리 접근과 관련된 문제를 방지하기 위한 최소한의 구조로서, null 자체가 '잘못된 처리'를 의미함
  • '무언가를 갖고 있지 않은 상태'와 '무언가 설정되지 않은 상태'는 그 자체로 의미가 있는 상태임
  • 이러한 상태조차 존재하지 않음을 뜻하는게 null인데, 이러한 상태들에 null을 사용하면 큰 손실을 불러일으킬 수 있음

null을 리턴/전달하지 말기

  • null 체크를 하지 않으려면, 애초에 null을 다루지 않게 만들어야 함
    • null을 리턴하지 않는 설계: 메서드에서 null을 리턴하지 않게 작성하는 것
    • null을 전달하지 않는 설계: 메서드에서 null을 변수에 할당하지 않는 것
  • '무언가를 갖고 있지 않은 상태'나 '무언가 설정되지 않은 상태'를 포함해서 항상 인스턴스가 존재하게 만들면, null 예외가 발생할 걱정을 안해도 됨
  • 따라서 null 체크 자체가 필요하지 않음

null 안전

  • null 안전이란 null에 의한 오류가 아예 발생하지 않게 만드는 구조임
  • 일부 프로그래밍 언어는 프로그래밍 언어 자체가 null 안전 사양을 지원하기도 함
  • null 안전을 구현하기 위한 기능으로 null 안전 자료형이 있음
  • null 안전 자료형은 null을 아예 저장할 수 없게 만드는 자료형
  • 코틀린은 기본적으로 모든 자료형에 null 안전 자료형을 사용해서, null을 할당하는 코드는 컴파일조차 되지 않음

📌 예외를 catch하고서 무시하는 코드

  • try-catch로 예외를 catch해 놓고도, 별다른 처리를 하고 있지 않는 코드는 굉장히 사악한 로직임

원인 분석을 어렵게 만듦

  • 이러한 코드의 문제는 오류가 나도, 오류를 탐지할 방법이 없어진다는 것임
  • 데이터에 문제가 생기는 등 잘못된 상태에 빠져도, 외부에서는 아무런 문제가 없는 것처럼 보이게 만듦
  • 잘못된 데이터를 이용하다가 또 다른 잘못된 데이터가 만들어질 가능성도 생김
  • 예외를 무시하면, 잘못된 상태를 곧바로 확인할 수 없고 이후 서비스 사용자에 의해 보고될 가능성이 높음
  • 이 때, 어느 시점에 어떤 코드에서 문제가 발생했는지 찾기 힘들어서 코드를 하나하나 확인하면서 원인을 찾아야 해서, 개발자의 시간과 체력을 상당히 많이 고갈시킴

문제가 발생했다면 소리치기

  • 이러한 상황을 피하려면, 잘못된 상태에 어떠한 관용도 베풀어서는 안됨
  • 예외를 확인했다면 곧바로 통지, 기록하는 것이 좋음
  • 가드가 있는 생성자도 잘못된 상태를 막아주는 설계임
  • 문제가 발생하는 즉시 소리쳐서 잘못된 상태를 막는 구조가 좋은 구조임

📌 설계 질서를 파괴하는 메타 프로그래밍

  • 프로그램 실행 중에 해당 프로그램 구조 자체를 제어하는 프로그래밍을 메타 프로그래밍이라고 부름
  • 자바에서 메타 프로그래밍을 활용해 클래스 구조를 읽고 쓸 때 리플렉션 API를 사용함
  • 이를 통해 일반적인 프로그래밍에서는 접근할 수 없는 부분까지 접근할 수 있음
  • 그래서 프로그래머들은 메타 프로그래밍을 '흑마법'이라고 표현하기도 함
  • 메타프로그래밍은 용법과 의도를 제대로 이해하지 못하고 사용했을 때, 전체적인 설계를 무너뜨릴 수도 있는 매우 위험한 기술임

리플렉션으로 인한 클래스 구조와 값 변경 문제

  • 리플렉션을 사용하면 final로 지정한 변수의 값도 바꿀 수 있고, private으로 외부에서 접근하지 못하게 만든 변수에도 접근할 수 있음
  • 따라서 리플렉션을 남용하면, '잘못된 상태로부터 클래스를 보호하는 설계'와 '영향 범위를 최대한 좁게 만드는 설계'가 아무런 의미도 갖지 못하게 됨
  • 집을 외부 침입으로부터 안전하게 보호하려 시도는 했지만, 뒷문을 열어 놓은 것과 같음

자료형의 장점을 살리지 못하는 하드 코딩

  • 자바로 대표되는 정적 자료형 언어는 정적 분석으로 정확한 코드 분석이 가능하다는 장점이 있지만, 메타 프로그래밍은 이러한 장점조차 무너뜨림
  • 클래스의 인스턴스를 만들 때는 일반적으로 new 키워드를 사용함
  • 하지만 리플렉션을 사용하면, 메타 정보를 기반으로 인스턴스를 생성할 수 있음
  • generateInstance 메서드는 패키지 이름과 클래스 이름을 문자열로 전달하면, 해당 클래스의 인스턴스를 생성해서 리턴함
  • 이를 활용하면 패키지 이름과 클래스 이름을 문자열로 전달해서, 클래스를 생성할 수 있음


  • 그런데 IntelliJ IDEA와 같은 IDE에는 클래스와 메서드 등의 이름을 한꺼번에 변경해 주는 기능이 있음
  • 하지만 generateInstance로 만든 인스턴스는 해당하지 않음
  • generateInstance 메서드의 매개변수로 전달된 것이 단순한 문자열이기 때문임
  • IDE의 정적 분석을 사용하면, 어떤 클래스가 어디에서 참조되고 있는지 정확하게 분석할 수 있음
  • 따라서 이름 변경 기능은 이름을 참조하는 모든 곳을 쉽게 일관 변경할 수 있는 것임
  • 하지만 단순하게 문자열로 하드코딩되어 있는 클래스 이름을 그 클래스의 자료형이라고 인식하지 않아서, 이름 변경 대상에서 제외됨
  • IDE의 정적 분석 기능은 이름 변경 이외에도 정의한 위치로 점프, 참조하고 있는 위치 전체 검색 등 개발의 효율성과 정확성 향상에 도움을 주지만, 메타 프로그래밍을 남용하면, 이와 같은 개발 도구의 지원을 받을 수 없게 됨

단점을 이해하고 용도를 한정해서 사용하기

  • 메타 프로그래밍을 사용하면, 뭔가 특별한 능력을 배운 것만 같아 기분이 좋을 수도 있지만, 단점을 이해하지 못하고 사용하면, 유지 보수와 변경이 정말 힘들어짐
  • 마치 '흑마법'처럼 사용할 때는 큰 힘을 얻은 것 같지만, 결국 거대한 악을 불러들여 스스로를 파멸하는 것과 같음
  • 메타 프로그래밍을 사용하고 싶다면 시스템 분석 용도로 한정하거나, 아주 작은 범위에서만 활용하는 등 리스크를 최소화 해야함

📌 기술 중심 패키징

  • 패키지를 구분할 때도 폴더를 적절하게 나누지 않으면, 악마를 불러들일 수 있음
  • 구조에 따라 폴더와 패키지를 나누는 것을 기술 중심 패키징이라고 부름
  • 여러 웹 프레임워크에서 MVC 구조를 사용하는데, 이와 같은 형태가 기술 중심 패키징이라고 할 수 있음
  • 프레임워크의 표준적인 구조가 기술 중심 패키징이다 보니, 이에 따라서 폴더 구조를 기술 중심 패키징에 맞게 구성하기 쉬움


  • 비즈니스 개념을 나타내는 클래스를 비즈니스 클래스라고 함
  • 비즈니스 클래스를 기술 중심 패키징에 따라 폴더를 구분하면, 관련성을 알기 매우 힘들어짐. 파일 단위로 묶여 응집도가 낮아짐
  • 비즈니스 클래스는 관련된 비즈니스 개념을 기준으로 폴더를 구분하는 것이 좋음 (서로 관련있는 클래스들을 한 폴더에 넣어 구분함)
  • 그러면 서로 관련없는 클래스끼리 참조할 위험을 방지할 수 있음
  • 또한 관련된 개념끼리 모여 있으므로, 사양이 달라졌을 때 달라진 사양과 관련된 폴더 내부의 파일만 읽으면 되고 관련 파일을 이리저리 찾아 다니지 않아도 됨

📌 샘플 코드 복사해서 붙여넣기

  • 프로그래밍 언어와 프레임워크의 공식 사이트에서 샘플 코드를 그대로 복사하고 붙여 넣어 구현하면, 설계 측면에서 좋지 않은 구조가 되기 쉬움
  • 샘플 코드는 어디까지나 언어의 사양과 라이브러리의 기능을 설명하기 위해 작성된 것임
  • 유지 보수성과 변경 용이성까지 생각해서 작성된 코드가 아님
  • '샘플 코드가 이렇게 작성되어 있다'라는 이유로 그대로 구현하면, 순식간에 조악한 로직이 만들어져서 악마를 불러들임
  • 샘플 코드는 어디까지나 참고만 하고, 클래스 구조를 잘 설계해서 사용해라

📌 은 탄환

  • 새로운 기술과 방법을 익히면, 곧바로 써 보고 싶어짐
  • 새로운 기술은 개발 현장의 모든 문제를 해결해 줄 것처럼 매력적으로 보이기도 함
  • 하지만 현실에서 발생하는 여러 문제는 특정 기술 하나로 해결할 수 있을 정도로 단순하지 않고, 대부분 매우 복잡하게 얽혀 있음
  • 문제를 해결하기 위해서, 상황을 고려하지 않고 '자신이 알고 있는 편리한 기술'을 활용해 버리면 문제가 해결되기는커녕 반대로 더 심각해 질 수 있음
  • 서양에서 늑대 인간과 악마는 은으로 만들어진 총알로 죽일 수 있다고 알려짐
  • 그래서 어떤 문제를 해결하는 비장의 무기, 묘책을 '은 탄환'이라고 부름
  • 하지만 소프트웨어 개발에는 은 탄환이 없음


  • 이 책에서 설명하는 방법들은 사양 변경이 있을 때, 이를 조금이라도 쉽게 하기 위한 설계를 설명함
  • 따라서 '실험적인 목적으로 개발한 프로토타입'또는 '사양 변경을 할 필요가 없는 소프트웨어'에 대해서는 큰 효과가 없음
  • 이러한 상황에서 이 책의 설계를 적용한다면, 쓸데없이 설계 비용만 커질 뿐임


  • 중요한 것은 어떤 문제가 있을 때, 어떤 방법이 해당 문제에 효과적인지, 비용이 더 들지는 않는지 평가하고 판단하는 자세임
  • 문제와 목적을 머릿속에 새겨 두고, 적절한 기술을 선택할 수 있도록 노력하자
  • 설계에 Best라는 것은 없고, 항상 Better를 목표로 할 뿐임

❓ Questions

❓ 자바스크립트에도 null 안전 자료형이 있나?

  • 코틀린이나 스위프트, 다트에서는 기본적으로 자료형이 null 안전 자료형(null safety)이라 null을 넣을 수 없음
  • null을 넣고 싶다면 자료형 뒤에 ?를 넣어야 함
var nullable: String? = "Hello"
var nonNullable: String = "Hello"
nullable = null // 가능
nonNullable = null // 오류 발생
var nullable: String? = "Hello"
var nonNullable: String = "Hello"
nullable = nil // 가능
nonNullable = nil // 오류 발생
String? nullable = "Hello";
String nonNullable = "Hello";
nullable = null; // 가능
nonNullable = null; // 오류 발생
  • 그럼 자바스크립트에도 이러한 null 연산 자료형이 있거나, null 안전성을 강화할 방법이 있을까?


  • 자바스크립트는 변수에 타입을 지정할 수 없기 때문에 null 안전 자료형이 없는것 같다
  • 또한 타입스크립트에서도 null 안전 자료형은 따로 없는 것 같다.
  • 하지만 타입스크립트에 strictNullChecks 옵션을 사용하면 null 안전성을 강화할 수 있다고 한다.
// 원래
let nullable: string = "Hello";
nullable = null; // 가능

// strictNullChecks 옵션 사용 시
let nullable: string | null = "Hello";
let nonNullable: string = "Hello";
nullable = null; // 가능
nonNullable = null; // 오류 발생

❓ 자바스크립트의 메타 프로그래밍

  • 찾아보니 자바스크립트에서 메타 프로그래밍을 사용할 때 Reflect 객체, Proxy 객체 등을 사용할 수 있다고 함
  • 이에 대한 내용은 양이 많고 난이도가 높아보여서 나중에 제대로 한 번 공부해서 정리해 봐야겠다.

results matching ""

    No results matching ""